/* TotalDcmListerPluginSettings.h -- Source file for class TotalDcmListerPluginSettings
*
* TotalDcmLister - A lister plugin for Total Commander for listing DICOM File content.
*
* Copyright (C) 2015 Captain Stark
*
* This software may be modified and distributed under the terms of the MIT license.
* See the COPYRIGHT file at the top-level directory of this distribution for details.
*/

#include "stdafx.h"
#include "TotalDcmListerPluginSettings.h"
#include "DicomAttributeGroup.h"

#include <sstream>
#include <pugixml.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace pugi;

#define DEFAULT_SettingsFileName L"TotalDcmListerPlugin.settings.xml"
#define DEFAULT_DisplayMissingAttributes true
#define DEFAULT_DefaultValueForMissingAttributes L"n/a"

/// <summary>
/// Initializes a new instance of the <see cref="TotalDcmListerPluginSettings"/> class.
/// </summary>
/// <param name="settingsLocation">Location of the settings file.</param>
TotalDcmListerPluginSettings::TotalDcmListerPluginSettings(const wstring& settingsLocation)
: m_displayMissingAttributes(DEFAULT_DisplayMissingAttributes),
  m_defaultValueForMissingAttributes(DEFAULT_DefaultValueForMissingAttributes)
{
   m_settingsFileName = settingsLocation + L"\\" + DEFAULT_SettingsFileName;

   xml_parse_result result = m_settingsDocument.load_file(m_settingsFileName.c_str());

   if (xml_parse_status::status_ok != result.status)
   {
      CreateDefaultSettings();
   }

   m_displayMissingAttributes =
      GetAppSettingAsBool(L"DisplayMissingAttributes", DEFAULT_DisplayMissingAttributes);
      
   m_defaultValueForMissingAttributes =
      GetAppSettingAsString(L"DefaultValueForMissingAttributes", DEFAULT_DefaultValueForMissingAttributes);

   LoadDicomDataToDisplay();
}

/// <summary>
/// Finalizes an instance of the <see cref="TotalDcmListerPluginSettings"/> class.
/// </summary>
TotalDcmListerPluginSettings::~TotalDcmListerPluginSettings()
{
}


/// <summary>
/// Gets a value indicating whether missing attributes shall be displayed or not.
/// </summary>
/// <returns>The setting's value.</returns>
const bool TotalDcmListerPluginSettings::DisplayMissingAttributes()
{
   return m_displayMissingAttributes;
}

/// <summary>
/// Gets the default value to be displayed for missing attributes.
/// </summary>
/// <returns>The setting's value.</returns>
const wstring TotalDcmListerPluginSettings::DefaultValueForMissingAttributes()
{
   return m_defaultValueForMissingAttributes;
}

/// <summary>
/// Gets a list of the DICOM data to be displayed. 
/// The list contains groups of DICOM data to be displayed while each group contains attributes to display.
/// </summary>
/// <returns>A list of the DICOM data to be displayed.</returns>
const vector<DicomAttributeGroup> TotalDcmListerPluginSettings::DicomDataToDisplay()
{
   return m_dicomDataToDisplay;
}

bool TotalDcmListerPluginSettings::GetAppSettingAsBool(const wstring& settingName, const bool defaultValue)
{
   wostringstream query;
   query << L"/TotalDcmListerSettings/AppSettings/Setting[@name='" << settingName << L"']/@value";
   
   xpath_node setting = m_settingsDocument.select_node(query.str().c_str());

   if (setting)
   {
      return setting.attribute().as_bool(defaultValue);
   }

   return defaultValue;
}

wstring TotalDcmListerPluginSettings::GetAppSettingAsString(const wstring& settingName, const wstring& defaultValue)
{
   wostringstream query;
   query << L"/TotalDcmListerSettings/AppSettings/Setting[@name='" << settingName << L"']/@value";
   
   xpath_node setting = m_settingsDocument.select_node(query.str().c_str());

   if (setting)
   {
      return setting.attribute().as_string(defaultValue.c_str());
   }

   return defaultValue;
}

void TotalDcmListerPluginSettings::LoadDicomDataToDisplay()
{
   xpath_node dicomDataSetting = m_settingsDocument.select_node(L"/TotalDcmListerSettings/DicomDataToDisplay");

   if (nullptr == dicomDataSetting)
   {
      return;
   }

   for (xml_node group : dicomDataSetting.node().children(L"Group"))
   {
      wstring groupHeader = group.attribute(L"header").as_string();

      vector<DicomAttribute> items;

      for (xml_node item : group.children(L"Attribute"))
      {
         wstring tag = item.attribute(L"tag").as_string();
         wstring name = item.attribute(L"name").as_string();

         items.push_back(DicomAttribute(tag, name));
      }

      m_dicomDataToDisplay.push_back(DicomAttributeGroup(groupHeader, items));
   }
}

void TotalDcmListerPluginSettings::CreateDefaultSettings()
{
   m_settingsDocument.reset();

   SetAppSettingAsBool(L"DisplayMissingAttributes", DEFAULT_DisplayMissingAttributes);
   SetAppSettingAsString(L"DefaultValueForMissingAttributes", DEFAULT_DefaultValueForMissingAttributes);

   SetDicomAttributeToDisplay(L"Instance Details", L"(0008,0016)", L"SOP Class UID");
   SetDicomAttributeToDisplay(L"Instance Details", L"(0008,0018)", L"SOP Instance UID");

   SetDicomAttributeToDisplay(L"Patient Details", L"(0010,0010)", L"Patient Name");
   SetDicomAttributeToDisplay(L"Patient Details", L"(0010,0020)", L"Patient ID");
   SetDicomAttributeToDisplay(L"Patient Details", L"(0010,0021)", L"Issuer Of Patient ID");

   SetDicomAttributeToDisplay(L"Equipment Details", L"(0008,0070)", L"Manufacturer");
   SetDicomAttributeToDisplay(L"Equipment Details", L"(0008,1090)", L"Manufacturer's Model Name");
   SetDicomAttributeToDisplay(L"Equipment Details", L"(0018,1000)", L"Device Serial Number");
   SetDicomAttributeToDisplay(L"Equipment Details", L"(0018,1020)", L"Software Version(s)");

   m_settingsDocument.save_file(m_settingsFileName.c_str());
}

void TotalDcmListerPluginSettings::SetAppSettingAsBool(const wstring& name, const bool value)
{
   xml_node setting = FindOrCreateAppSettingWithName(name);

   setting.attribute(L"name").set_value(name.c_str());
   setting.attribute(L"value").set_value(value);
}

void TotalDcmListerPluginSettings::SetAppSettingAsString(const wstring& name, const wstring& value)
{
   xml_node setting = FindOrCreateAppSettingWithName(name);

   setting.attribute(L"name").set_value(name.c_str());
   setting.attribute(L"value").set_value(value.c_str());
}

xml_node TotalDcmListerPluginSettings::FindOrCreateAppSettingWithName(const wstring& name)
{
   xml_node root = m_settingsDocument.child(L"TotalDcmListerSettings");
   if (nullptr == root)
   {
      root = m_settingsDocument.append_child(L"TotalDcmListerSettings");
   }

   xml_node appSettings = root.child(L"AppSettings");
   if (nullptr == appSettings)
   {
      appSettings = root.append_child(L"AppSettings");
   }

   xml_node setting = appSettings.find_child_by_attribute(L"Setting", L"name", name.c_str());
   if (nullptr == setting)
   {
      setting = appSettings.append_child(L"Setting");
      setting.append_attribute(L"name");
      setting.append_attribute(L"value");
   }

   return setting;
}

void TotalDcmListerPluginSettings::SetDicomAttributeToDisplay(
   const wstring& group, const wstring& tag, const wstring& name)
{
   xml_node root = m_settingsDocument.child(L"TotalDcmListerSettings");
   if (nullptr == root)
   {
      root = m_settingsDocument.append_child(L"TotalDcmListerSettings");
   }

   xml_node dicomDataToDisplay = root.child(L"DicomDataToDisplay");
   if (nullptr == dicomDataToDisplay)
   {
      dicomDataToDisplay = root.append_child(L"DicomDataToDisplay");
   }

   xml_node groupNode = dicomDataToDisplay.find_child_by_attribute(L"Group", L"header", group.c_str());
   if (nullptr == groupNode)
   {
      groupNode = dicomDataToDisplay.append_child(L"Group");
      groupNode.append_attribute(L"header").set_value(group.c_str());
   }

   xml_node attributeNode = groupNode.find_child_by_attribute(L"Attribute", L"tag", tag.c_str());
   if (nullptr == attributeNode)
   {
      attributeNode = groupNode.append_child(L"Attribute");
      attributeNode.append_attribute(L"tag").set_value(tag.c_str());
   }

   attributeNode.append_attribute(L"name").set_value(name.c_str());
}
